﻿//checked
using System;
using System.IO;
using System.Reflection;
using net.entity.framework.common;

namespace net.entity.framework.query {
    public abstract class QueryRoot : IQueryStateCompiler {

        internal static readonly QueryRoot[] EmptyRoots = new QueryRoot[0];

        //these are useful for building QueryRoot from constants


        #region Operators

        //for integer constants
        public static implicit operator QueryRoot(int value) {
            return Constant.Create(value);
        }

        //for double constants
        public static implicit operator QueryRoot(double value) {
            return Constant.Create(value);
        }

        //for string constants
        public static implicit operator QueryRoot(string value) {
            return Constant.Create(value);
        }

        //for datetime constants
        public static implicit operator QueryRoot(DateTime value) {
            return Constant.Create(value);
        }

        //for enum constants
        public static implicit operator QueryRoot(Enum value) {
            return Constant.Create(value);
        }

        #endregion

        public QueryRoot Desc {
            get { return DescendingModifier.Create(this); }
        }

        public virtual void CompileState(CompiledQueryState state) {}

        public abstract void EmitQuery(TextWriter writer, CompiledQueryState state);
    }

    /// <summary>
    /// Represents an expression that performs Airthmetic and Comparision operations.
    /// </summary>
    public abstract class Expression : QueryRoot {
        #region Implicit Cast from known value type constant

        public static implicit operator Expression(bool value) {
            return Constant.Create(value);
        }

        public static implicit operator Expression(int value) {
            return Constant.Create(value);
        }

        public static implicit operator Expression(double value) {
            return Constant.Create(value);
        }

        public static implicit operator Expression(string value) {
            return Constant.Create(value);
        }

        public static implicit operator Expression(DateTime value) {
            return Constant.Create(value);
        }

        public static implicit operator Expression(Enum value) {
            return Constant.Create(value);
        }

        #endregion

        #region Comparison

        //these overloaded operators are syntatical-sugar for Eql 
        //comparison conditions.

        public static QueryClause operator ==(Expression lhs, Expression rhs) {
            return ComparisonClause.Create(Operations.Equal, lhs, rhs);
        }

        public static QueryClause operator !=(Expression lhs, Expression rhs) {
            return ComparisonClause.Create(Operations.NotEqual, lhs, rhs);
        }

        public static QueryClause operator <(Expression lhs, Expression rhs) {
            return ComparisonClause.Create(Operations.LessThan, lhs, rhs);
        }

        public static QueryClause operator >(Expression lhs, Expression rhs) {
            return ComparisonClause.Create(Operations.GreaterThan, lhs, rhs);
        }

        public static QueryClause operator <=(Expression lhs, Expression rhs) {
            return ComparisonClause.Create(Operations.LessThanEqual, lhs, rhs);
        }

        public static QueryClause operator >=(Expression lhs, Expression rhs) {
            return ComparisonClause.Create(Operations.GreaterThanEqual, lhs, rhs);
        }


        //public BetweenClause Between(Expression lhs, Expression rhs) {
        //    return BetweenClause.Create(this, lhs, rhs);
        //}

        #region In Clase

        //public QueryClause In(int[] list) {
        //    return In((IEnumerable<int>) list);
        //}

        //public QueryClause In(double[] list) {
        //    return In((IEnumerable<double>) list);
        //}

        //public QueryClause In(string[] list) {
        //    return In((IEnumerable<string>) list);
        //}

        //public QueryClause In(DateTime[] list) {
        //    return In((IEnumerable<DateTime>) list);
        //}

        //public QueryClause In<T>(IEnumerable<T> items) {
        //    return Vector.Create(items.ToList()).CreateInClause(this);
        //}

        //public QueryClause In(Expression listExpression) {
        //    return listExpression.CreateInClause(this);
        //}

        //protected virtual QueryClause CreateInClause(Expression item) {
        //    throw new Exception(string.Format("Can't create InClause of type {0}", GetType().Name));
        //}

        //public QueryClause In(QueryBuilder nestedQuery) {
        //    return NestedClause.Create(this, nestedQuery);
        //}

        #endregion

        //public QueryClause LooseMatch(string rhs)
        //{
        //    if (rhs.IndexOf('%') < 0)
        //    {
        //        return TyQl.Upper(this) == TyQl.Upper(rhs);
        //    }
        //    if (rhs == "%")
        //    {
        //        return true;
        //    }
        //    return TyQl.Upper(this).Like(TyQl.Upper(rhs));
        //}

        //public QueryClause Like(Expression rhs) {
        //    return LikeClause.Create(this, rhs);
        //}


        //public QueryClause IsNull {
        //    get { return IsNullExpression.Create(this); }
        //}

        #endregion

        #region Airthmetic

        //public static Function operator +(Expression lhs, Expression rhs) {
        //    return Function.CreateInfix(lhs, "+", rhs);
        //}

        //public static Function operator -(Expression lhs, Expression rhs) {
        //    return Function.CreateInfix(lhs, "-", rhs);
        //}

        //public static Function operator *(Expression lhs, Expression rhs) {
        //    return Function.CreateInfix(lhs, "*", rhs);
        //}

        //public static Function operator /(Expression lhs, Expression rhs) {
        //    return Function.CreateInfix(lhs, "/", rhs);
        //}

        //public static Function operator %(Expression lhs, Expression rhs) {
        //    return Function.CreateInfix(lhs, "%", rhs);
        //}

        #endregion

        //public static Function operator -(Expression rhs)
        //{
        //    return Function.CreatePrefix("-", rhs);
        //}

        //public T Meta<T>()
        //{
        //    throw new Exception("The TyQl Meta method can only be used in a LINQ expression as a placeholder for a query result.  It cannot be called directly.");
        //}

        //public virtual Expression CloneSetTyQlInfo(Type valueTypeIfKnown, TargetProperty propertyInfoIfKnown) {
        //    return SetTyQlInfoExpression.Create(this, valueTypeIfKnown, propertyInfoIfKnown);
        //}

        public virtual bool TryGetConstant(out object value) {
            value = null;
            return false;
        }

        public override int GetHashCode() {
            throw new NotImplementedException();
        }

        public override bool Equals(object obj) {
            throw new NotImplementedException();
        }
    }

    /// <summary>
    /// Represents a one-to-one relation between EqlEntity fields and database columns.
    /// </summary>
    public class Terminal : Expression {
        internal static readonly Terminal[] EmptyTerminals = new Terminal[0];

        #region Members

        private readonly AliasInfo aliasInfo;
        private readonly string columnName;
        private readonly Type valueType;
        private readonly PropertyInfo propertyInfo;

        #endregion

        public AliasInfo AliasInfo {
            get { return aliasInfo; }
        }

        public string ColumnName {
            get { return columnName; }
        }

        public Type ValueType {
            get { return valueType; }
        }

        public PropertyInfo PropertyInfo {
            get { return propertyInfo; }
        }

        #region Constructor/Create

        protected Terminal(AliasInfo aliasInfo, string columnName, Type valueType, PropertyInfo propertyInfo) {
            this.aliasInfo = aliasInfo;
            this.columnName = columnName;
            this.valueType = valueType;
            this.propertyInfo = propertyInfo;
        }

        public static Terminal Create(AliasInfo aliasInfo, string columnName, Type valueTypeIfKnown,
                                      PropertyInfo propertyInfoIfKnown) {
            return new Terminal(aliasInfo, columnName, valueTypeIfKnown, propertyInfoIfKnown);
        }

        #endregion

        public override void CompileState(CompiledQueryState state) {
            state.LookupOrAddAlias(aliasInfo);
        }

        public override void EmitQuery(TextWriter writer, CompiledQueryState state) {
            var alias = state.LookupAlias(aliasInfo);
            writer.Write("{0}.[{1}]", alias, columnName);
        }
    }

    public class DescendingModifier : QueryRoot {
        public static QueryRoot Create(QueryRoot root) {
            return root;
        }


        public override void EmitQuery(TextWriter writer, CompiledQueryState state) {
            throw new NotImplementedException();
        }
    }

    public abstract class Constant : Expression {
        public static Constant<T> Create<T>(T value) {
            return Constant<T>.Create(value);
        }
    }

    public sealed class NullConstant : Constant {
        private static readonly NullConstant theItem = new NullConstant();

        public static NullConstant Create() {
            return theItem;
        }

        private NullConstant() {}

        public override bool TryGetConstant(out object value) {
            value = null;
            return false;
        }

        //protected override QueryClause CreateInClause(Expression item)
        //{
        //    return BooleanConstant.False;
        //}

        public override void EmitQuery(TextWriter writer, CompiledQueryState state) {
            writer.Write("null");
        }
    }

    public sealed class Constant<T> : Constant {
        public static Constant<T> Create(T item) {
            return new Constant<T>(item);
        }

        private readonly T value;

        private Constant(T value) {
            this.value = value;
        }

        public override bool TryGetConstant(out object outValue) {
            outValue = value;
            return true;
        }

        public override void EmitQuery(TextWriter writer, CompiledQueryState state) {
            writer.Write(SqlUtil.Literal(value));
        }

        //public override Type ValueTypeIfKnown
        //{
        //    get { return typeof(T); }
        //}
    }

    public sealed class Operations : IQueryStateCompiler {
        public static readonly Operations Equal = new Operations("=");
        public static readonly Operations NotEqual = new Operations("<>");
        public static readonly Operations LessThan = new Operations("<");
        public static readonly Operations GreaterThan = new Operations(">");
        public static readonly Operations LessThanEqual = new Operations("<=");
        public static readonly Operations GreaterThanEqual = new Operations(">=");

        public static readonly Operations And = new Operations("AND");
        public static readonly Operations Or = new Operations("OR");

        public static readonly Operations Like = new Operations("LIKE");
        public static readonly Operations Between = new Operations("BETWEEN");

        private readonly string operation = "";

        private Operations(string operation) {
            this.operation = operation;
        }

        public void CompileState(CompiledQueryState state) {
            //Do nothing
        }

        public void EmitQuery(TextWriter writer, CompiledQueryState state) {
            writer.Write(" {0} ", operation);
        }

        public override string ToString() {
            return operation;
        }
    }

    public class EqlStar : Expression
    {
        private AliasInfo aliasInfo;

        private EqlStar(AliasInfo aliasInfo) {
            this.aliasInfo = aliasInfo;
        }

        public static EqlStar Create() {
            return new EqlStar(new AliasInfo("","",null));
        }

        public static EqlStar Create(AliasInfo aliasInfo)
        {
            return new EqlStar(aliasInfo);
        }

        public override void CompileState(CompiledQueryState state)
        {
            state.LookupOrAddAlias(aliasInfo);
        }

        public override void EmitQuery(TextWriter writer, CompiledQueryState state) {
            var alias = state.LookupAlias(aliasInfo);
            writer.Write("[{0}].*",alias);
        }
    }
}